#ifndef PW_PBL_META_COMPRESS_HPP
#define PW_PBL_META_COMPRESS_HPP

#include <algorithm>
#include <span>
#include "../BitField.hpp"
#include "../Numbers.hpp"
#include "../Concepts/Core.hpp"
#include "Auto.hpp"

namespace pw_pbl::Meta::Compress
{
	struct SpanOutput
	{
		std::span<uint8_t>::iterator current;
		std::span<uint8_t>::iterator end;
		constexpr SpanOutput(std::span<uint8_t> buf) : current(buf.begin()), end(buf.end()) {}

		constexpr void put(uint8_t v)
		{
			if (current < end)
				*current++ = v;
			else
				end = {};
		}
		constexpr void put(std::span<const uint8_t> data)
		{
			auto len = end - current;
			if (len > 0)
			{
				auto cplen = std::min(size_t(len), data.size());
				std::copy(data.begin(), data.end(), current);
				current += cplen;
			}
			else if (data.size() != 0)
				end = {};
		}
		constexpr void fill(uint8_t v, size_t count)
		{
			auto len = end - current;
			if (len > 0)
			{
				auto fllen = std::min(size_t(len), count);
				std::fill_n(current, count, v);
				current += fllen;
			}
			else if (count != 0)
				end = {};
		}
		constexpr bool has_error() const { return end == std::span<uint8_t>::iterator{}; }
	};

	struct CountOutput
	{
		size_t count = 0;
		constexpr CountOutput() : count(0) {}

		constexpr void put(uint8_t) { count++; }
		constexpr void put(std::span<const uint8_t> data) { count += data.size(); }
		constexpr void fill(uint8_t, size_t count) { count += count; }
		constexpr bool has_error() const { return false; }
	};

	template <typename T>
	concept ccCompressor = requires(const T &compress, std::span<const uint8_t> src, std::span<uint8_t> out) {
		{ compress.compressed_size(src) } -> std::same_as<size_t>;
		{ compress.compress(src, out) } -> std::same_as<bool>;
		{ compress.decompress(src, out) } -> std::same_as<bool>;
	};

	struct ZeroStrip
	{
		struct Tag : BitFieldStruct<uint8_t>
		{
			PW_PBL_BITFIELD(len, 0, UBitInt<7>);
			PW_PBL_BITFIELD(is_zero, 7, bool);
		};
		static constexpr uint8_t MAX_LEN = UBitInt<7>(EMax);

	private:
		static constexpr size_t zo_len(std::span<const uint8_t>::iterator start, std::span<const uint8_t>::iterator end)
		{
			auto nz = start;
			for (; nz < end; nz++)
				if (*nz != 0)
					break;
			return std::min(size_t(nz - start), size_t(MAX_LEN));
		}
		static constexpr size_t nz_len(std::span<const uint8_t>::iterator start, std::span<const uint8_t>::iterator end)
		{
			auto zo = start;
			for (; zo < end; zo++)
				if (*zo == 0)
					break;
			return std::min(size_t(zo - start), size_t(MAX_LEN));
		}
		template <typename BUF>
		static constexpr bool compress_imp(std::span<const uint8_t> data, BUF &&buf)
		{
			auto data_current = data.begin();
			auto data_end = data.end();
			for (; data_current < data_end;)
			{
				if (*data_current == 0)
				{
					auto len = zo_len(data_current, data_end);
					buf.put(Tag{}.len().with(len).is_zero().with(true).raw);
					data_current += len;
				}
				else
				{
					auto len = nz_len(data_current, data_end);
					buf.put(Tag{}.len().with(len).is_zero().with(false).raw);
					buf.put({data_current, len});
					data_current += len;
				}
			}
			return !buf.has_error();
		}

	public:
		static constexpr size_t compressed_size(std::span<const uint8_t> data)
		{
			CountOutput buf{};
			compress_imp(data, buf);
			return buf.count;
		}
		static constexpr bool compress(std::span<const uint8_t> data, std::span<uint8_t> out) { return compress_imp(data, SpanOutput(out)); }
		static constexpr bool decompress(std::span<const uint8_t> data, std::span<uint8_t> out)
		{
			auto data_current = data.begin();
			auto data_end = data.end();
			SpanOutput buf(out);
			for (; data_current < data_end;)
			{
				Tag tag{*data_current++};
				auto is_zero = tag.is_zero().read();
				auto len = tag.len().read();
				if (is_zero)
					buf.fill(0, len);
				else
				{
					buf.put({data_current, len});
					data_current += len;
				}
			}
			return !buf.has_error();
		}
	};

	template <typename T, size_t N, Meta::Auto Compressor>
	struct Compressed
	{
		std::array<uint8_t, N> data;

		static constexpr size_t ORIGIN_SIZE = sizeof(T);
		static constexpr size_t SIZE = N;

		inline bool decompress_to(T &out) const { return Compressor().decompress(data, std::span{(uint8_t *)&out, sizeof(T)}); }

		static consteval ttPatchArray<T> test_only_decompress(const Compressed &self)
		{
			std::array<uint8_t, sizeof(ttPatchArray<T>)> buf;
			Compressor().decompress(self.data, buf);
			return std::bit_cast<ttPatchArray<T>>(buf);
		}
	};

	template <Meta::Auto DATA, Meta::Auto Compressor> requires ccCompressor<decltype(Compressor())>
	struct Compress
	{
		using type = std::remove_cvref_t<decltype(DATA())>;
		static constexpr auto RAW_DATA = std::bit_cast<std::array<uint8_t, sizeof(type)>>(DATA());
		static constexpr size_t ZLEN = Compressor().compressed_size(RAW_DATA);
		using compressed = Compressed<type, ZLEN, Compressor>;

		static constexpr compressed RESULT = [] -> compressed {
			std::array<uint8_t, ZLEN> out;
			Compressor().compress(RAW_DATA, out);
			return {out};
		}();
	};
} // namespace pw_pbl::Meta::Compress

namespace pw_pbl::Meta::inline literals
{
	template <Meta::Auto DATA, Meta::Auto Compressor = Compress::ZeroStrip{}>
	static constexpr const Compress::Compress<DATA, Compressor>::compressed &MetaCompress = Compress::Compress<DATA, Compressor>::RESULT;
}
// namespace pw_pbl::Meta

#endif // PW_PBL_META_COMPRESS_HPP
